﻿/// <reference path="custommanipulation.d.ts" />
/// <reference path="custommanipulation.d.ts" />
import $ = require("jquery");
import ko = require("knockout");

interface GesturedElement extends HTMLElement {
    gesture: TypedMsGesture;
}

interface TypedMsGesture extends MSGesture {
    pointerType;
}

enum GestureState {
    None,
    ScrollX,
    ScrollY,
    ScrollFree,
    Zoom,
    Hold,
    HoldDrag,
    Tap,
}

class GestureManager {
    constructor($el: JQuery) {
        this.$el = $el;
        this.el = <GesturedElement>($el[0]);

        var msGesture = new MSGesture();
        msGesture.target = $el[0];
        this.el.gesture = <TypedMsGesture>(msGesture);
        this.el.gesture.pointerType = null;

        this.bindEvents();
        this.prepareTree();
    }

    private prepareTree() {
        this.el.style.touchAction="none";
        this.$el.find("*").each((index, elem) => {});
    }

    private bindPointer = (event) => {
        var el = this.el;
        // Process pointer.
        //  Attach first contact and track device.
        if (el.gesture.pointerType === null) {
            el.gesture.addPointer(event.pointerId);
            el.gesture.pointerType = event.pointerType;
        }
        // Attach subsequent contacts from same device.
        else if (event.pointerType === el.gesture.pointerType) {
            el.gesture.addPointer(event.pointerId);
        }
        // New gesture recognizer for new pointer type.
        else {
            var msGesture = new MSGesture();
            msGesture.target = el;
            el.gesture = <TypedMsGesture>(msGesture);
            el.gesture.pointerType = event.pointerType;
            el.gesture.addPointer(event.pointerId);
        }
    }

    private el: GesturedElement;
    private $el: JQuery;
    private state: GestureState;

    private bindEvents() {
        this.el.addEventListener("pointerdown", this.bindPointer, false);
        this.el.addEventListener("MSGestureStart", this.gestureStart, false);
        this.el.addEventListener("MSGestureChange", this.gestureChange, false);
        this.el.addEventListener("MSGestureEnd", this.gestureEnd, false);
        this.el.addEventListener("MSGestureTap", this.gestureTap, false);
        this.el.addEventListener("MSGestureHold", this.gestureHold, false);
    }

    private gestureStart = (ev: MSGestureEvent) => {
        var target = document.elementFromPoint(ev.clientX, ev.clientY);
        this.$target = $(target);

        if (this.state == GestureState.Hold && this.holdGesture == ev.gestureObject) {
            this.state = GestureState.HoldDrag;
        } else {
            if (Math.abs(ev.translationX) > Math.abs(ev.translationY)) {
                this.state = GestureState.ScrollX;
            } else {
                this.state = GestureState.ScrollY;
            }
        }
    };

    private gestureChange = (ev: MSGestureEvent) => {
        var scrollData: IScrollChangedArgs = {relx: -ev.translationX, rely: -ev.translationY, x: ev.offsetX, y: ev.offsetY};
        var zoomData: IZoomChangedArgs = { rel: ev.scale, relx: -ev.translationX, rely: -ev.translationY, x: ev.offsetX, y: ev.offsetY };

        if (ev.scale != 1) {
            this.state = GestureState.Zoom;
        }

        switch (this.state) {
            case GestureState.ScrollX:
                scrollData.rely = 0;
                this.$target.trigger("manip.scroll", scrollData);
                break;
            case GestureState.ScrollY:
                scrollData.relx = 0;
                this.$target.trigger("manip.scroll", scrollData);
                break;
            case GestureState.ScrollFree:
                this.$target.trigger("manip.scroll", scrollData);
                break;
            case GestureState.Zoom:
                this.$target.trigger("manip.zoom", zoomData);
                break;
            case GestureState.HoldDrag:
                var $over = $(document.elementFromPoint(ev.clientX, ev.clientY));
                $over.trigger("manip.dragover", { clientX: ev.clientX, clientY: ev.clientY });
                this.$target.trigger("manip_holddrag", { clientX: ev.clientX, clientY: ev.clientY });
                break;
            default:
                break;
        }
    };

    private gestureEnd = (ev: MSGestureEvent) => {
        if (this.state == GestureState.HoldDrag) {
            this.releaseHoldDrag();
        } else {
            this.state = GestureState.None;
        }
    };

    private gestureTap = (ev: MSGestureEvent) => {
        var target = document.elementFromPoint(ev.clientX, ev.clientY);
        this.state = GestureState.Tap;
        $(target).trigger("manip.tap", {offsetX: ev.offsetX, offsetY: ev.offsetY});
    };

    private gestureHold = (ev: MSGestureEvent) => {
        if (ev.detail == ev.MSGESTURE_FLAG_BEGIN) {
            this.holdGesture = ev.gestureObject;
            this.state = GestureState.Hold;
            var target = document.elementFromPoint(ev.clientX, ev.clientY);
            this.$target = $(target);
            this.$target.trigger("manip_hold", { clientX: ev.clientX, clientY: ev.clientY });
        } else if (ev.detail == ev.MSGESTURE_FLAG_END) {
            this.holdTimeOut = setTimeout(this.releaseHold, 10);
        }
    };

    private releaseHold = () => {
        if (this.state == GestureState.HoldDrag) {
            return;
        }
        this.$target.trigger("manip_release.hold"); //.hold
        this.state = GestureState.None;
    };

    private releaseHoldDrag() {
        if (this.state != GestureState.HoldDrag) {
            return;
        }
        this.$target.trigger("manip_release.drag"); //.drag
        this.state = GestureState.None;
    }

    private nextChangeEventTimeout: number = null;
    private nextChangeEvent: MSGestureEvent = null;
    
    private holdGesture: TypedMsGesture;
    private holdTimeOut: number;
    private $target: JQuery;
}

$.fn.GestureRoot = function () {
    this.gestures = new GestureManager($(this));
};

class GestureRoot {
    public static create() {
        ko.bindingHandlers.GestureRoot = {
            init: element => {
                var gestureManager = new GestureManager($(element));
            },
            update: () => {}
        }
    }
}

export = GestureRoot;